package com.feinno.urcs.group.http.auth;

import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;

/**
 * Created by wuzhiguo on 15-5-12.
 */
public class HttpDigest {

	public static final String HEADER_AUTHORIZATION = "Authorization";
	public static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
	public static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info";
	public static final String HEADER_DIGEST = "Digest";

	public static final String ALGORITHM_MD5 = "MD5";
	public static final String ALGORITHM_MD5_SESS = "MD5-sess";

	public static final String QOP_AUTH = "auth";
	public static final String QOP_AUTH_INT = "auth-int";

	// 必须字段
	private String method;
	private String body;
	private String algorithm;
	private String qop;
	private String username;
	private String passwd;
	private String cnonce;
	private String nonce;
	private String nc;
	private String realm;
	private String uri;

	// 计算摘要字段
	private String response;
	private String a1;
	private String a2;

	public HttpDigest(String method, String body, String algorithm, String qop, String username, String passwd, String cnonce, String nonce, String nc, String realm, String uri) {
		this.method = method;
		this.body = body;
		this.algorithm = algorithm;
		this.qop = qop;
		this.username = username;
		this.passwd = passwd;
		this.cnonce = cnonce;
		this.nonce = nonce;
		this.nc = nc;
		this.realm = realm;
		this.uri = uri;
	}

	public String digest() throws Exception {
		if (isNullOrEmpty(qop)) {
			response = (MD5(MD5(A1()) + ":" + nonce + ":" + MD5(A2())));
		} else if (qop.equals(QOP_AUTH) || qop.equals(QOP_AUTH_INT)) {
			response = (MD5(MD5(A1()) + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + MD5(A2())));
		} else {
			throw new Exception(String.format("invalid qop:%s ", qop));
		}

		return response;
	}

	private String A1() throws Exception {
		if (isNullOrEmpty(algorithm) || algorithm.equals(ALGORITHM_MD5)) {
			a1 = (username + ":" + realm + ":" + passwd);
		} else if (algorithm.equals(ALGORITHM_MD5_SESS)) {
			a1 = (MD5(username + ":" + realm + ":" + passwd) + ":" + nonce + ":" + cnonce);
		} else {
			throw new Exception(String.format("invalid algorithm:%s ", algorithm));
		}

		return a1;
	}

	private String A2() throws Exception {
		if (isNullOrEmpty(qop) || qop.equals(QOP_AUTH)) {
			a2 = (method + ":" + uri);
		} else if (qop.equals(QOP_AUTH_INT)) {
			a2 = (method + ":" + uri + ":" + MD5(body));
		} else {
			throw new Exception(String.format("invalid qop:%s ", qop));
		}

		return a2;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(String.format("[HttpDigest]\n"));
		sb.append(String.format("method:%s\n", method));
		sb.append(String.format("body:%s\n", body));
		sb.append(String.format("algorithm:%s\n", algorithm));
		sb.append(String.format("qop:%s\n", qop));
		sb.append(String.format("username:%s\n", username));
		sb.append(String.format("passwd:%s\n", passwd));
		sb.append(String.format("cnonce:%s\n", cnonce));
		sb.append(String.format("nonce:%s\n", nonce));
		sb.append(String.format("nc:%s\n", nc));
		sb.append(String.format("realm:%s\n", realm));
		sb.append(String.format("uri:%s\n", uri));
		sb.append(String.format("response:%s\n", response));
		sb.append(String.format("a1:%s\n", a1));
		sb.append(String.format("a2:%s\n", a2));
		return sb.toString();
	}

	// 下面是一些公共静态方法
	public static String MD5(String data) throws Exception {
		MessageDigest md = MessageDigest.getInstance("MD5");
		md.update(data.getBytes("utf-8"));
		return bytes2hex(md.digest());
	}

	public static String bytes2hex(byte[] bytes) {
		StringBuilder hexsb = new StringBuilder();
		for (int i = 0; i < bytes.length; i++) {
			String temp = (Integer.toHexString(bytes[i] & 0XFF));
			if (temp.length() == 1) {
				temp = "0" + temp;
			}
			hexsb.append(temp);
		}

		return hexsb.toString();
	}

	public static boolean isNullOrEmpty(String str) {
		return str == null ? true : str.equals("");
	}

	public static String generateRandomString() {
		return UUID.randomUUID().toString().replace("-", "");
	}

	public static String buildAuthorizationHeaderValue(String algorithm, String qop, String username, String cnonce, String nonce, String nc, String realm, String uri, String response, String opaque) {
		StringBuilder sb = new StringBuilder();
		sb.append(String.format("%s ", HEADER_DIGEST));
		if (!isNullOrEmpty(algorithm))
			sb.append(String.format("algorithm=%s,", algorithm));
		if (!isNullOrEmpty(qop))
			sb.append(String.format("qop=%s,", qop));
		if (!isNullOrEmpty(username))
			sb.append(String.format("username=%s,", username));
		if (!isNullOrEmpty(cnonce))
			sb.append(String.format("cnonce=%s,", cnonce));
		if (!isNullOrEmpty(nonce))
			sb.append(String.format("nonce=%s,", nonce));
		if (!isNullOrEmpty(nc))
			sb.append(String.format("nc=%s,", nc));
		if (!isNullOrEmpty(realm))
			sb.append(String.format("realm=%s,", realm));
		if (!isNullOrEmpty(uri))
			sb.append(String.format("uri=%s,", uri));
		if (!isNullOrEmpty(response))
			sb.append(String.format("response=%s,", response));
		if (!isNullOrEmpty(opaque))
			sb.append(String.format("opaque=%s,", opaque));

		if (sb.charAt(sb.length() - 1) == ',') {
			sb.setLength(sb.length() - 1);
		}

		return sb.toString();
	}

	public static String buildWWWAuthenticateHeaderValue(String algorithm, String qop, String nonce, String realm, String opaque) {
		StringBuilder sb = new StringBuilder();
		sb.append(String.format("%s ", HEADER_DIGEST));
		if (!isNullOrEmpty(algorithm))
			sb.append(String.format("algorithm=%s,", algorithm));
		if (!isNullOrEmpty(qop))
			sb.append(String.format("qop=%s,", qop));
		if (!isNullOrEmpty(nonce))
			sb.append(String.format("nonce=%s,", nonce));
		if (!isNullOrEmpty(realm))
			sb.append(String.format("realm=%s,", realm));
		if (!isNullOrEmpty(opaque))
			sb.append(String.format("opaque=%s,", opaque));

		if (sb.charAt(sb.length() - 1) == ',') {
			sb.setLength(sb.length() - 1);
		}

		return sb.toString();
	}

	public static String buildAuthenticationInfoHeaderValue(String nextnonce) {
		StringBuilder sb = new StringBuilder();
		sb.append(String.format("%s ", HEADER_DIGEST));
		if (!isNullOrEmpty(nextnonce))
			sb.append(String.format("nextnonce=%s", nextnonce));

		return sb.toString();
	}

	public static Map<String, String> parseDigestHeader(String digestHeaderValue) {
		Map<String, String> valueMap = new TreeMap<String, String>();
		int index = digestHeaderValue.indexOf(HEADER_DIGEST);
		if (index >= 0) {
			String headerValue = digestHeaderValue.substring(index + HEADER_DIGEST.length());
			String[] headerValueArray = headerValue.split(",");
			if (headerValueArray != null) {
				for (int i = 0; i < headerValueArray.length; i++) {
					String valueItem = headerValueArray[i];
					String[] valueItemArray = valueItem.split("=");
					if (valueItemArray != null && valueItemArray.length == 2) {
						valueMap.put(valueItemArray[0].trim(), trimQuoteChar(valueItemArray[1].trim()));
					}
				}
			}
		}

		return valueMap;
	}

	public static Map<String, String> parseDigestHeaderPlus(String digestHeaderValue) {
		final char equalChar = '=';
		final char commaChar = ',';
		final char quoteChar = '"';
		Map<String, String> valueMap = new TreeMap<String, String>();
		int index = digestHeaderValue.indexOf(HEADER_DIGEST);
		if (index >= 0) {
			String remainString = digestHeaderValue.substring(index + HEADER_DIGEST.length()).trim();
			int equalIndex = 0;
			int commaIndex = 0;
			while ((equalIndex = remainString.indexOf(equalChar)) > 0) {
				String key = remainString.substring(0, equalIndex).trim();
				remainString = remainString.substring(equalIndex + 1).trim();
				if (remainString.length() > 0) {
					if (remainString.charAt(0) == quoteChar) {
						int quoteIndex = remainString.indexOf(quoteChar, 1);
						if (quoteIndex > 0) {
							String value = remainString.substring(1, quoteIndex).trim();
							valueMap.put(key, value);

							remainString = remainString.substring(quoteIndex + 1).trim();
							if (remainString.length() > 0 && remainString.charAt(0) == commaChar) {
								remainString = remainString.substring(1).trim();
							} else {
								break;
							}
						} else {
							break;
						}
					} else {
						commaIndex = remainString.indexOf(commaChar);
						if (commaIndex >= 0) {
							String value = remainString.substring(0, commaIndex).trim();
							remainString = remainString.substring(commaIndex + 1).trim();
							valueMap.put(key, value);
						} else {
							String value = remainString.trim();
							valueMap.put(key, value);
							break;
						}
					}
				}
			}
		}

		return valueMap;
	}

	public static String trimOneChar(String str, char ch) {
		String newstr = str;
		if (str != null && str.length() > 0) {
			int beginIndex = 0;
			int endIndex = str.length();
			if (str.charAt(0) == ch) {
				beginIndex++;
			}
			if (str.charAt(str.length() - 1) == ch) {
				endIndex--;
			}
			if (endIndex < beginIndex) {
				endIndex = beginIndex;
			}
			newstr = str.substring(beginIndex, endIndex);
		}

		return newstr;
	}

	public static String trimQuoteChar(String str) {
		return trimOneChar(str, '"');
	}

	public static String packOneChar(String str, char ch) {
		return String.format("%c%s%c", ch, str, ch);
	}

	public static String packQuoteChar(String str) {
		return packOneChar(str, '"');
	}

}
